name: CI/CD Pipeline on: push: branches: [main, develop] pull_request: branches: [main, develop] workflow_dispatch: env: NODE_VERSION: '20.x' REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm install --workspaces --legacy-peer-deps - name: Run ESLint run: npm run lint --if-present || echo "No lint script found" - name: Type check run: npx tsc --noEmit test: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm install --workspaces --legacy-peer-deps - name: Run unit tests run: npm test --if-present || echo "No test script found" - name: Upload coverage reports uses: codecov/codecov-action@v4 if: always() with: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage/coverage-final.json flags: unittests fail_ci_if_error: false build: name: Build runs-on: ubuntu-latest needs: [lint, test] steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm install --workspaces --legacy-peer-deps - name: Prepare build directories run: | mkdir -p /tmp/dist mkdir -p dist - name: Build application run: npm run build - name: Verify build output run: | if [ ! -f "dist/index.html" ]; then echo "Error: Build failed - index.html not found" exit 1 fi echo "Build successful - all artifacts generated" ls -lah dist/ - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: dist path: dist/ retention-days: 7 e2e-tests: name: E2E Tests runs-on: ubuntu-latest needs: build steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm install --workspaces --legacy-peer-deps - name: Install Playwright browsers run: npx playwright install --with-deps chromium - name: Download build artifacts uses: actions/download-artifact@v4 with: name: dist path: dist/ - name: Run E2E tests run: npm run test:e2e --if-present || echo "No E2E tests configured" - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: playwright-report/ retention-days: 7 security-scan: name: Security Scan runs-on: ubuntu-latest permissions: contents: read security-events: write actions: read steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm install --workspaces --legacy-peer-deps - name: Run npm audit run: npm audit --audit-level=moderate || true - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'sarif' output: 'trivy-results.sarif' - name: Upload Trivy results to GitHub Security uses: github/codeql-action/upload-sarif@v4 if: always() with: sarif_file: 'trivy-results.sarif' docker-build: name: Build Docker Image runs-on: ubuntu-latest needs: [lint, test, build] if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=sha,prefix={{branch}}- type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max deploy-staging: name: Deploy to Staging runs-on: ubuntu-latest needs: [docker-build] if: github.ref == 'refs/heads/develop' environment: name: staging url: https://staging.codeforge.example.com steps: - uses: actions/checkout@v4 - name: Deploy to staging run: | echo "Deploying to staging environment..." echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop-${{ github.sha }}" - name: Notify deployment uses: 8398a7/action-slack@v3 if: always() with: status: ${{ job.status }} text: 'Staging deployment ${{ job.status }}' webhook_url: ${{ secrets.SLACK_WEBHOOK }} env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} deploy-production: name: Deploy to Production runs-on: ubuntu-latest needs: [docker-build, e2e-tests] if: github.ref == 'refs/heads/main' environment: name: production url: https://codeforge.example.com steps: - uses: actions/checkout@v4 - name: Deploy to production run: | echo "Deploying to production environment..." echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main-${{ github.sha }}" - name: Notify deployment uses: 8398a7/action-slack@v3 if: always() with: status: ${{ job.status }} text: 'Production deployment ${{ job.status }}' webhook_url: ${{ secrets.SLACK_WEBHOOK }} env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}