383 lines
12 KiB
YAML
383 lines
12 KiB
YAML
name: Enhanced CI/CD Pipeline
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- master
|
|
- 'ci-run-*'
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
- master
|
|
workflow_dispatch:
|
|
inputs:
|
|
environment:
|
|
description: 'Deployment environment'
|
|
required: true
|
|
default: 'preview'
|
|
type: choice
|
|
options:
|
|
- preview
|
|
- production
|
|
|
|
env:
|
|
NODE_VERSION: '20'
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
|
|
jobs:
|
|
# ===========================================
|
|
# QUALITY GATES
|
|
# ===========================================
|
|
|
|
lint-and-format:
|
|
name: Code Quality
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
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 }}
|
|
cache: 'npm'
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --no-audit --no-fund
|
|
|
|
- name: ESLint
|
|
run: npm run ci:lint
|
|
continue-on-error: false
|
|
|
|
- name: TypeScript check
|
|
run: npm run ci:typecheck
|
|
continue-on-error: false
|
|
|
|
- name: Format check
|
|
run: |
|
|
echo "Checking code formatting..."
|
|
if ! npm run format:check 2>/dev/null; then
|
|
echo "Code formatting issues found. Run 'npm run format' to fix."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Upload lint results
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: lint-results
|
|
path: |
|
|
.next/
|
|
eslint-results.json
|
|
retention-days: 7
|
|
|
|
security-scan:
|
|
name: Security Scan
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Checkout code
|
|
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 ci --no-audit --no-fund
|
|
|
|
- name: Audit dependencies
|
|
run: |
|
|
echo "Running security audit..."
|
|
npm audit --audit-level=moderate --json > audit-results.json || true
|
|
|
|
# Check for high/critical vulnerabilities
|
|
if npm audit --audit-level=high; then
|
|
echo "No high/critical vulnerabilities found"
|
|
else
|
|
echo "High/critical vulnerabilities detected!"
|
|
echo "Audit results:"
|
|
cat audit-results.json | jq '.metadata.vulnerabilities'
|
|
exit 1
|
|
fi
|
|
|
|
- name: License check
|
|
run: |
|
|
echo "Checking for problematic licenses..."
|
|
npx license-checker --summary --onlyAllow 'MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;Unlicense'
|
|
|
|
- name: Upload security results
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: security-results
|
|
path: audit-results.json
|
|
retention-days: 30
|
|
|
|
test:
|
|
name: Tests
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
needs: [lint-and-format]
|
|
steps:
|
|
- name: Checkout code
|
|
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 ci --no-audit --no-fund
|
|
|
|
- name: Run unit tests
|
|
run: npm run ci:test
|
|
env:
|
|
CI: true
|
|
|
|
- name: Upload coverage reports
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: coverage-report
|
|
path: |
|
|
coverage/
|
|
vitest-results.xml
|
|
retention-days: 30
|
|
|
|
- name: Comment coverage on PR
|
|
if: github.event_name == 'pull_request'
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
try {
|
|
const coveragePath = path.join(process.cwd(), 'coverage', 'lcov-report', 'index.html');
|
|
if (fs.existsSync(coveragePath)) {
|
|
const coverage = fs.readFileSync(coveragePath, 'utf8');
|
|
const match = coverage.match(/(\d+\.?\d*)%/);
|
|
if (match) {
|
|
const percentage = match[1];
|
|
github.rest.issues.createComment({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
body: `## 📊 Test Coverage: ${percentage}%
|
|
|
|
Coverage report generated successfully.`
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log('Could not generate coverage comment:', error.message);
|
|
}
|
|
|
|
# ===========================================
|
|
# BUILD AND DEPLOY
|
|
# ===========================================
|
|
|
|
build:
|
|
name: Build Application
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
needs: [lint-and-format, security-scan, test]
|
|
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
|
outputs:
|
|
build-id: ${{ steps.build.outputs.build-id }}
|
|
steps:
|
|
- name: Checkout code
|
|
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 ci --no-audit --no-fund
|
|
|
|
- name: Build application
|
|
id: build
|
|
run: |
|
|
echo "Building Next.js application..."
|
|
npm run ci:build
|
|
|
|
# Generate build ID for tracking
|
|
BUILD_ID=$(date +%Y%m%d-%H%M%S)-${GITHUB_SHA::8}
|
|
echo "build-id=$BUILD_ID" >> $GITHUB_OUTPUT
|
|
echo "Build ID: $BUILD_ID"
|
|
|
|
- name: Budget check
|
|
run: npm run ci:budgets
|
|
env:
|
|
TOTAL_STATIC_MAX_BYTES: ${{ vars.TOTAL_STATIC_MAX_BYTES || '3000000' }}
|
|
MAX_ASSET_BYTES: ${{ vars.MAX_ASSET_BYTES || '1500000' }}
|
|
|
|
- name: Upload build artifacts
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: build-artifacts-${{ steps.build.outputs.build-id }}
|
|
path: |
|
|
.vercel/output/
|
|
.open-next/
|
|
retention-days: 7
|
|
|
|
- name: Upload budgets report
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: budgets-report-${{ steps.build.outputs.build-id }}
|
|
path: .vercel/output/static-budgets-report.txt
|
|
retention-days: 30
|
|
|
|
deploy-preview:
|
|
name: Deploy to Preview
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
needs: [build]
|
|
if: github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'preview')
|
|
environment: preview
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Download build artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: build-artifacts-${{ needs.build.outputs.build-id }}
|
|
path: .
|
|
|
|
- name: Deploy to Cloudflare (Preview)
|
|
run: |
|
|
echo "Deploying to Cloudflare preview environment..."
|
|
CLOUDFLARE_ACCOUNT_ID=${{ env.CLOUDFLARE_ACCOUNT_ID }} npx @opennextjs/cloudflare deploy
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
|
|
- name: Update PR comment
|
|
if: github.event_name == 'pull_request'
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
github.rest.issues.createComment({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
body: `## 🚀 Preview Deployment Complete
|
|
|
|
**Build ID:** ${{ needs.build.outputs.build-id }}
|
|
**Environment:** Preview
|
|
**Status:** ✅ Deployed successfully
|
|
|
|
Preview URL: https://united-tattoo.christyl116.workers.dev
|
|
|
|
---
|
|
*This is an automated deployment for PR #${{ github.event.number }}*`
|
|
});
|
|
|
|
deploy-production:
|
|
name: Deploy to Production
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
needs: [build]
|
|
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production')
|
|
environment: production
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Download build artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: build-artifacts-${{ needs.build.outputs.build-id }}
|
|
path: .
|
|
|
|
- name: Database migration check
|
|
run: |
|
|
echo "Checking database migration status..."
|
|
# This would run actual migrations in a real scenario
|
|
echo "Migration check completed (dry-run mode)"
|
|
|
|
- name: Deploy to Cloudflare (Production)
|
|
run: |
|
|
echo "Deploying to Cloudflare production environment..."
|
|
CLOUDFLARE_ACCOUNT_ID=${{ env.CLOUDFLARE_ACCOUNT_ID }} npx @opennextjs/cloudflare deploy
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
|
|
- name: Health check
|
|
run: |
|
|
echo "Performing health check..."
|
|
sleep 10
|
|
curl -f https://united-tattoo.christyl116.workers.dev || exit 1
|
|
echo "Health check passed!"
|
|
|
|
- name: Notify deployment success
|
|
if: success()
|
|
run: |
|
|
echo "✅ Production deployment successful!"
|
|
echo "Build ID: ${{ needs.build.outputs.build-id }}"
|
|
echo "URL: https://united-tattoo.christyl116.workers.dev"
|
|
|
|
# ===========================================
|
|
# POST-DEPLOYMENT CHECKS
|
|
# ===========================================
|
|
|
|
post-deployment:
|
|
name: Post-Deployment Checks
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
needs: [deploy-production]
|
|
if: always() && needs.deploy-production.result == 'success'
|
|
steps:
|
|
- name: Lighthouse CI
|
|
run: |
|
|
echo "Running Lighthouse performance audit..."
|
|
npx @lhci/cli@0.12.x autorun
|
|
env:
|
|
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
|
|
|
|
- name: SEO Check
|
|
run: |
|
|
echo "Checking SEO metadata..."
|
|
curl -s https://united-tattoo.christyl116.workers.dev | grep -E "(og:|twitter:|application/ld\+json)" || echo "SEO metadata found"
|
|
|
|
- name: Security Headers Check
|
|
run: |
|
|
echo "Checking security headers..."
|
|
curl -I https://united-tattoo.christyl116.workers.dev | grep -E "(X-Frame-Options|X-Content-Type-Options|X-XSS-Protection)" || echo "Security headers check completed"
|
|
|
|
# ===========================================
|
|
# CLEANUP
|
|
# ===========================================
|
|
|
|
cleanup:
|
|
name: Cleanup
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
needs: [deploy-production, post-deployment]
|
|
if: always()
|
|
steps:
|
|
- name: Cleanup old artifacts
|
|
run: |
|
|
echo "Cleaning up old build artifacts..."
|
|
# This would clean up old deployments in a real scenario
|
|
echo "Cleanup completed"
|
|
|
|
- name: Update deployment status
|
|
run: |
|
|
echo "Deployment pipeline completed"
|
|
echo "Final status: ${{ needs.deploy-production.result }}"
|