diff --git a/.gitea/workflows/pipeline.yml b/.gitea/workflows/pipeline.yml new file mode 100644 index 0000000..234776d --- /dev/null +++ b/.gitea/workflows/pipeline.yml @@ -0,0 +1,214 @@ +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 + container: + image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim + steps: + - uses: actions/checkout@v4 + + - 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 + container: + image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim + steps: + - uses: actions/checkout@v4 + + - name: Run Ruff + run: uvx ruff check . + working-directory: api + + api-integration: + name: API Integration Tests + runs-on: ubuntu-latest + container: + image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim + 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 + minio: + image: bitnami/minio:latest + env: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + MINIO_DEFAULT_BUCKETS: reactbin-test + steps: + - uses: actions/checkout@v4 + + - 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://minio: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 + + # ── 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 }}/${{ github.repository_owner }}/api:${{ github.ref_name }} + ${{ vars.REGISTRY }}/${{ github.repository_owner }}/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 }}/${{ github.repository_owner }}/ui:${{ github.ref_name }} + ${{ vars.REGISTRY }}/${{ github.repository_owner }}/ui:latest