Skip to content

CI/CD Pipeline

CI/CD Pipeline #475

Workflow file for this run

name: CI/CD Pipeline
on:
pull_request:
branches: [ main, develop ]
push:
branches: [ main, develop ]
workflow_dispatch:
# Cancel previous runs for the same PR/branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: '18'
jobs:
# Check if files have changed to skip unnecessary jobs
changes:
name: Detect Changes
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
source: ${{ steps.filter.outputs.source }}
config: ${{ steps.filter.outputs.config }}
i18n: ${{ steps.filter.outputs.i18n }}
styles: ${{ steps.filter.outputs.styles }}
tests: ${{ steps.filter.outputs.tests }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for file changes
uses: dorny/paths-filter@v3
id: filter
with:
filters: |
source:
- 'app/**/*.{ts,tsx,js,jsx}'
- 'components/**/*.{ts,tsx,js,jsx}'
- 'lib/**/*.{ts,tsx,js,jsx}'
- 'middleware.ts'
- '*.{ts,tsx,js,jsx}'
config:
- 'package.json'
- 'pnpm-lock.yaml'
- 'tsconfig.json'
- 'next.config.ts'
- 'tailwind.config.js'
- 'eslint.config.mjs'
- 'commitlint.config.mjs'
- 'postcss.config.mjs'
- '.prettierrc.json'
- '.prettierignore'
tests:
- '__tests__/**/*.{ts,tsx,js,jsx}'
- '**/*.test.{ts,tsx,js,jsx}'
- '**/*.spec.{ts,tsx,js,jsx}'
i18n:
- 'messages/**/*.json'
- 'i18n/**/*.{ts,js}'
- 'scripts/**/*i18n*'
styles:
- 'styles/**/*.css'
- 'app/globals.css'
# Install dependencies and cache them
install:
name: Install Dependencies
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.source == 'true' || needs.changes.outputs.config == 'true' || needs.changes.outputs.tests == 'true' || needs.changes.outputs.styles == 'true'
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.pnpm-store
node_modules
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
# Code formatting and linting checks
format-and-lint:
name: Format and Lint Check
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [changes, install]
if: needs.changes.outputs.source == 'true' || needs.changes.outputs.config == 'true' || needs.changes.outputs.styles == 'true'
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check code formatting
run: pnpm run format:check
- name: Run ESLint on changed files
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
RANGE="${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
elif [[ "${{ github.event.before }}" != "" && "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]]; then
RANGE="${{ github.event.before }}..${{ github.sha }}"
else
RANGE="$(git hash-object -t tree /dev/null)..${{ github.sha }}"
fi
echo "Using diff range: $RANGE"
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT "$RANGE" | grep -E '\.(js|jsx|ts|tsx)$' | tr '\n' ' ' || true)
if [ -n "$CHANGED_FILES" ]; then
echo "Running ESLint on changed files: $CHANGED_FILES"
pnpm exec eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache $CHANGED_FILES
else
echo "No JS/TS files changed in this event"
fi
# TypeScript type checking
type-check:
name: TypeScript Type Check
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [changes, install]
if: needs.changes.outputs.source == 'true' || needs.changes.outputs.config == 'true'
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run TypeScript type check
run: pnpm run type-check
# Build the application
build:
name: Build Application
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [changes, install]
if: needs.changes.outputs.source == 'true' || needs.changes.outputs.config == 'true'
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build application
run: pnpm run build
env:
# Provide dummy environment variables for build
BETTER_AUTH_SECRET: ci-build-only-secret-do-not-use-in-prod
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/agentifui
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: .next/
retention-days: 7
# Run tests
test:
name: Run Tests
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [changes, install]
if: needs.changes.outputs.tests == 'true' || needs.changes.outputs.source == 'true'
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run tests
run: pnpm test
# Security checks
security-check:
name: Security Check
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [changes, install]
if: needs.changes.outputs.source == 'true' || needs.changes.outputs.config == 'true'
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run dependency audit
run: pnpm audit --audit-level=moderate
- name: Scan for secrets
run: |
echo "Scanning for hardcoded secrets..."
# Check for potential secrets in source code
if grep -r -i -E "(api_key|apikey|secret|token|password)\s*[:=]\s*['\"][a-zA-Z0-9+/]{20,}['\"]" \
--include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
app components lib | grep -v -E "(placeholder|example|test|demo|sample|mock|dummy)"; then
echo "⚠️ Potential hardcoded secret detected"
exit 1
fi
echo "✅ No hardcoded secrets found"
# Final status check - all jobs must pass
ci-status:
name: CI Status Check
runs-on: ubuntu-latest
needs:
- changes
- format-and-lint
- type-check
- build
- test
- security-check
if: always()
permissions:
contents: read
steps:
- name: Check all jobs status
run: |
echo "Checking CI pipeline status..."
# Check if any job failed
if [[ "${{ needs.format-and-lint.result }}" == "failure" ]] || \
[[ "${{ needs.type-check.result }}" == "failure" ]] || \
[[ "${{ needs.build.result }}" == "failure" ]] || \
[[ "${{ needs.test.result }}" == "failure" ]] || \
[[ "${{ needs.security-check.result }}" == "failure" ]]; then
echo "❌ CI pipeline failed"
exit 1
fi
# Check if any required job was skipped when it shouldn't be
if [[ "${{ needs.changes.outputs.source }}" == "true" ]] || \
[[ "${{ needs.changes.outputs.config }}" == "true" ]] || \
[[ "${{ needs.changes.outputs.styles }}" == "true" ]]; then
if [[ "${{ needs.format-and-lint.result }}" == "skipped" ]]; then
echo "❌ Format and lint job was skipped"
exit 1
fi
fi
if [[ "${{ needs.changes.outputs.source }}" == "true" ]] || [[ "${{ needs.changes.outputs.config }}" == "true" ]]; then
if [[ "${{ needs.type-check.result }}" == "skipped" ]] || \
[[ "${{ needs.build.result }}" == "skipped" ]] || \
[[ "${{ needs.security-check.result }}" == "skipped" ]]; then
echo "❌ Required job was skipped"
exit 1
fi
fi
# Check test job when tests or source files changed
if [[ "${{ needs.changes.outputs.tests }}" == "true" ]] || [[ "${{ needs.changes.outputs.source }}" == "true" ]]; then
if [[ "${{ needs.test.result }}" == "skipped" ]]; then
echo "❌ Test job was skipped when it should run"
exit 1
fi
fi
echo "✅ All CI checks passed successfully"