name: Pipeline on: push: branches: [master] tags: ['v*'] pull_request: branches: [master] jobs: # ── UI ──────────────────────────────────────────────────────────────────────── ui-test: name: UI Tests runs-on: ubuntu-latest container: image: node:22-bullseye steps: - uses: actions/checkout@v4 - name: Install Firefox run: apt-get update -qq && apt-get install -y --no-install-recommends firefox-esr - name: Cache node_modules uses: actions/cache@v3 with: path: ui/node_modules key: npm-${{ hashFiles('ui/package-lock.json') }} restore-keys: npm- - name: Install dependencies run: npm ci working-directory: ui - name: Run tests run: FIREFOX_BIN=/usr/bin/firefox-esr npx ng test --watch=false working-directory: ui ui-lint: name: UI Lint runs-on: ubuntu-latest container: image: node:22-bullseye steps: - uses: actions/checkout@v4 - name: Cache node_modules uses: actions/cache@v3 with: path: ui/node_modules key: npm-${{ hashFiles('ui/package-lock.json') }} restore-keys: npm- - name: Install dependencies run: npm ci working-directory: ui - name: Run ESLint run: npm run lint working-directory: ui # ── API ─────────────────────────────────────────────────────────────────────── api-unit: name: API Unit Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install uv run: | curl -LsSf https://astral.sh/uv/install.sh | sh echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Cache uv store uses: actions/cache@v3 with: path: ~/.cache/uv key: uv-${{ hashFiles('api/uv.lock') }} restore-keys: uv- - name: Install dependencies run: uv sync --group dev working-directory: api - name: Run unit tests run: uv run pytest tests/unit/ -q working-directory: api env: DATABASE_URL: postgresql+asyncpg://u:p@localhost/db S3_ENDPOINT_URL: http://localhost:9000 S3_BUCKET_NAME: test S3_ACCESS_KEY_ID: key S3_SECRET_ACCESS_KEY: secret S3_REGION: us-east-1 API_BASE_URL: http://localhost:8000 JWT_SECRET_KEY: test-secret OWNER_USERNAME: testowner OWNER_PASSWORD: testpass api-lint: name: API Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Ruff run: | curl -LsSf https://astral.sh/uv/install.sh | sh ~/.local/bin/uvx ruff check . working-directory: api api-integration: name: API Integration Tests runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_USER: reactbin POSTGRES_PASSWORD: reactbin POSTGRES_DB: reactbin_test options: >- --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10 steps: - uses: actions/checkout@v4 - name: Start MinIO run: | docker run -d --name ci-minio \ --network container:$(hostname) \ -e MINIO_ROOT_USER=minioadmin \ -e MINIO_ROOT_PASSWORD=minioadmin \ quay.io/minio/minio server /data - name: Create MinIO bucket run: | curl -fsSL https://dl.min.io/client/mc/release/linux-amd64/mc -o /tmp/mc chmod +x /tmp/mc until /tmp/mc alias set local http://localhost:9000 minioadmin minioadmin; do sleep 2; done /tmp/mc mb local/reactbin-test - name: Install uv run: | curl -LsSf https://astral.sh/uv/install.sh | sh echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Cache uv store uses: actions/cache@v3 with: path: ~/.cache/uv key: uv-${{ hashFiles('api/uv.lock') }} restore-keys: uv- - name: Install dependencies run: uv sync --group dev working-directory: api - name: Run integration tests run: uv run pytest tests/integration/ -q working-directory: api env: TEST_DATABASE_URL: postgresql+asyncpg://reactbin:reactbin@postgres/reactbin_test DATABASE_URL: postgresql+asyncpg://reactbin:reactbin@postgres/reactbin_test S3_ENDPOINT_URL: http://localhost:9000 S3_BUCKET_NAME: reactbin-test S3_ACCESS_KEY_ID: minioadmin S3_SECRET_ACCESS_KEY: minioadmin S3_REGION: us-east-1 API_BASE_URL: http://localhost:8000 JWT_SECRET_KEY: test-secret OWNER_USERNAME: testowner OWNER_PASSWORD: testpass - name: Stop MinIO if: always() run: docker stop ci-minio || true && docker rm ci-minio || true # ── Image builds (tag-only, gated on all jobs) ──────────────────────────────── build-api: name: Build & Push API Image runs-on: ubuntu-latest needs: [ui-test, ui-lint, api-unit, api-lint, api-integration] if: startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ${{ vars.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - uses: docker/build-push-action@v6 with: context: ./api file: ./api/Dockerfile.prod push: true tags: | ${{ vars.REGISTRY }}/${{ vars.REPOSITORY }}/reactbin-api:${{ github.ref_name }} ${{ vars.REGISTRY }}/${{ vars.REPOSITORY }}/reactbin-api:latest build-ui: name: Build & Push UI Image runs-on: ubuntu-latest needs: [ui-test, ui-lint, api-unit, api-lint, api-integration] if: startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ${{ vars.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - uses: docker/build-push-action@v6 with: context: ./ui file: ./ui/Dockerfile.prod push: true tags: | ${{ vars.REGISTRY }}/${{ vars.REPOSITORY }}/reactbin-ui:${{ github.ref_name }} ${{ vars.REGISTRY }}/${{ vars.REPOSITORY }}/reactbin-ui:latest