name: CI Pipeline on: push: branches: [ main, develop, 'feature/*', 'hotfix/*' ] pull_request: branches: [ main, develop ] workflow_dispatch: env: NODE_VERSION: '18' PNPM_VERSION: '8.15.0' jobs: # Install dependencies and cache setup: name: Setup Dependencies runs-on: ubuntu-latest outputs: cache-key: ${{ steps.cache-keys.outputs.node-modules }} steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} registry-url: 'https://registry.npmjs.org' - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: ${{ env.PNPM_VERSION }} run_install: false - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - name: Generate cache keys id: cache-keys run: | echo "node-modules=${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}" >> $GITHUB_OUTPUT - name: Setup pnpm cache uses: actions/cache@v3 with: path: ${{ env.STORE_PATH }} key: ${{ steps.cache-keys.outputs.node-modules }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Cache node_modules uses: actions/cache@v3 id: cache-node-modules with: path: | node_modules packages/*/node_modules key: ${{ steps.cache-keys.outputs.node-modules }}-modules restore-keys: | ${{ runner.os }}-pnpm-modules- # Linting and formatting lint: name: Lint & Format Check runs-on: ubuntu-latest needs: setup steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: ${{ env.PNPM_VERSION }} - name: Restore dependencies uses: actions/cache@v3 with: path: | node_modules packages/*/node_modules key: ${{ needs.setup.outputs.cache-key }}-modules - name: Run ESLint run: pnpm lint - name: Check Prettier formatting run: pnpm format:check - name: TypeScript type check run: pnpm typecheck # Unit tests test: name: Unit Tests runs-on: ubuntu-latest needs: setup strategy: matrix: package: [api, worker, frontend, shared] steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: ${{ env.PNPM_VERSION }} - name: Restore dependencies uses: actions/cache@v3 with: path: | node_modules packages/*/node_modules key: ${{ needs.setup.outputs.cache-key }}-modules - name: Run tests for ${{ matrix.package }} run: pnpm --filter @ai-renamer/${{ matrix.package }} test - name: Generate coverage report run: pnpm --filter @ai-renamer/${{ matrix.package }} test:coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: file: ./packages/${{ matrix.package }}/coverage/lcov.info flags: ${{ matrix.package }} name: ${{ matrix.package }}-coverage fail_ci_if_error: false # Integration tests integration-test: name: Integration Tests runs-on: ubuntu-latest needs: setup services: postgres: image: postgres:16-alpine env: POSTGRES_PASSWORD: test_password POSTGRES_DB: ai_image_renamer_test options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 redis: image: redis:7-alpine options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 6379:6379 minio: image: minio/minio:latest env: MINIO_ROOT_USER: test_user MINIO_ROOT_PASSWORD: test_password options: >- --health-cmd "curl -f http://localhost:9000/minio/health/live" --health-interval 30s --health-timeout 20s --health-retries 3 ports: - 9000:9000 steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: ${{ env.PNPM_VERSION }} - name: Restore dependencies uses: actions/cache@v3 with: path: | node_modules packages/*/node_modules key: ${{ needs.setup.outputs.cache-key }}-modules - name: Setup test environment run: | cp .env.example .env.test echo "DATABASE_URL=postgresql://postgres:test_password@localhost:5432/ai_image_renamer_test" >> .env.test echo "REDIS_URL=redis://localhost:6379" >> .env.test echo "MINIO_ENDPOINT=localhost:9000" >> .env.test echo "MINIO_ACCESS_KEY=test_user" >> .env.test echo "MINIO_SECRET_KEY=test_password" >> .env.test - name: Run database migrations run: pnpm --filter @ai-renamer/api db:migrate - name: Run integration tests run: pnpm test:integration env: NODE_ENV: test # Build application build: name: Build Application runs-on: ubuntu-latest needs: [setup, lint, test] steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: ${{ env.PNPM_VERSION }} - name: Restore dependencies uses: actions/cache@v3 with: path: | node_modules packages/*/node_modules key: ${{ needs.setup.outputs.cache-key }}-modules - name: Build all packages run: pnpm build - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: build-artifacts path: | packages/*/dist packages/*/build retention-days: 7 # Docker build and test docker: name: Docker Build & Test runs-on: ubuntu-latest needs: [setup, lint, test] if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build Docker image uses: docker/build-push-action@v5 with: context: . target: production tags: ai-bulk-image-renamer:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max outputs: type=docker,dest=/tmp/image.tar - name: Upload Docker image artifact uses: actions/upload-artifact@v3 with: name: docker-image path: /tmp/image.tar retention-days: 1 - name: Test Docker image run: | docker load < /tmp/image.tar docker run --rm --name test-container -d \ -e NODE_ENV=test \ ai-bulk-image-renamer:${{ github.sha }} sleep 10 docker logs test-container docker stop test-container # Security scanning security: name: Security Scan runs-on: ubuntu-latest needs: setup steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: ${{ env.PNPM_VERSION }} - name: Restore dependencies uses: actions/cache@v3 with: path: | node_modules packages/*/node_modules key: ${{ needs.setup.outputs.cache-key }}-modules - name: Run npm audit run: pnpm audit --audit-level moderate continue-on-error: true - name: Run Snyk security scan uses: snyk/actions/node@master continue-on-error: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --severity-threshold=medium # Dependency updates check dependency-updates: name: Check Dependency Updates runs-on: ubuntu-latest if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: ${{ env.PNPM_VERSION }} - name: Check for outdated dependencies run: pnpm outdated - name: Create dependency update issue if: failure() uses: actions/github-script@v6 with: script: | github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: 'Dependency Updates Available', body: 'Automated check found outdated dependencies. Please review and update.', labels: ['dependencies', 'maintenance'] }) # Deployment readiness check deploy-check: name: Deployment Readiness runs-on: ubuntu-latest needs: [build, docker, security, integration-test] if: github.ref == 'refs/heads/main' steps: - name: Deployment ready run: | echo "✅ All checks passed - ready for deployment" echo "Build artifacts and Docker image are available" echo "Security scans completed" echo "Integration tests passed"