GitHub Actions CI/CD for Monorepo Projects

Complete GitHub Actions CI/CD guide for Turborepo monorepos: affected-only builds, parallel jobs, caching strategies, environment-based deploys, and security best practices.

E
ECOSIRE Research and Development Team
|19 مارچ، 20269 منٹ پڑھیں2.0k الفاظ|

GitHub ایکشنز CI/CD for Monorepo پروجیکٹس

monorepo کے لیے ایک CI/CD پائپ لائن کو سنگل ایپلی کیشن پائپ لائن کے مقابلے میں بنیادی طور پر مختلف چیلنج کا سامنا ہے: جب آپ کی 5 ایپلی کیشنز میں سے صرف 1 تبدیل ہوتی ہے تو آپ تیز، قابل بھروسہ پائپ لائن کیسے چلاتے ہیں؟ ہر کمٹ پر ہر چیز کو آسانی سے بنانا 2 منٹ کی پائپ لائن کو 15 منٹ کی رکاوٹ میں بدل دیتا ہے جسے ڈیولپرز بیچنگ کمٹ کے ذریعے کام کرتے ہیں — بالکل وہی جو آپ روکنے کی کوشش کر رہے ہیں۔

یہ گائیڈ Turborepo monorepo کے لیے ایک پروڈکشن GitHub ایکشن سیٹ اپ کا احاطہ کرتی ہے: صرف متاثرہ تعمیرات، متوازی جاب پر عمل درآمد، ٹربو ریموٹ کیشنگ انٹیگریشن، ماحول پر مبنی تعیناتی گیٹس، اور حفاظتی نمونے جو لاگز یا فورک شدہ PR رنز میں راز کو لیک ہونے سے روکتے ہیں۔

اہم ٹیک ویز

  • ٹربو کے متاثرہ پیکیج کا پتہ لگانے کے لیے چیک آؤٹ میں fetch-depth: 2 استعمال کریں۔
  • لنٹ چلائیں، ٹائپ چیک کریں، ٹیسٹ کریں، اور متوازی ملازمتوں میں تعمیر کریں - انہیں ترتیب وار نہ بنائیں
  • میٹرکس جابز آپ کو YAML کی نقل کیے بغیر متعدد Node.js ورژنز یا آپریٹنگ سسٹمز میں ٹیسٹ کرنے دیتی ہیں۔
  • ٹربو ریموٹ کیش اسناد کو GitHub راز کے طور پر اسٹور کریں، نہ کہ YAML میں ماحولیاتی متغیرات
  • دستی منظوری کی ضرورت کے لیے پروڈکشن ڈیپلائی جابز پر environment: تحفظ کے اصول استعمال کریں۔
  • paths-filter ایکشن آپ کو CI کو مکمل طور پر چھوڑنے دیتا ہے جب صرف دستاویزات یا غیر کوڈ فائلیں تبدیل ہوتی ہیں
  • لاگز پر کبھی بھی راز پرنٹ نہ کریں - رن ٹائم سے تیار کردہ رازوں کے لیے ::add-mask:: استعمال کریں
  • دوبارہ قابل استعمال ورک فلوز (workflow_call) متعدد ریپوزٹری ورک فلوز میں نقل کو ختم کرتا ہے

CI کے لیے مخزن کا ڈھانچہ

ایک اچھا CI سیٹ اپ آپ کے ترقیاتی کام کے فلو کا آئینہ دار ہے۔ یہاں وہ ڈھانچہ ہے جو Turborepo monorepo کے لیے کام کرتا ہے:

.github/
  workflows/
    ci.yml          — Runs on every PR and push to main
    deploy.yml      — Runs on push to main (after CI passes)
    security.yml    — Weekly security scans
    cleanup.yml     — Scheduled: delete stale preview deployments
  actions/
    setup-pnpm/
      action.yml    — Reusable: install Node + pnpm + cache

کور CI ورک فلو

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
    types: [opened, synchronize, reopened]

# Cancel in-progress runs when a new run starts
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}
  NODE_VERSION: '22'
  PNPM_VERSION: '10.28.0'

jobs:
  # Fast path: skip if only docs/assets changed
  changes:
    runs-on: ubuntu-latest
    outputs:
      code: ${{ steps.filter.outputs.code }}
      docs: ${{ steps.filter.outputs.docs }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            code:
              - 'apps/**/*.ts'
              - 'apps/**/*.tsx'
              - 'packages/**/*.ts'
              - 'turbo.json'
              - 'package.json'
              - 'pnpm-lock.yaml'
            docs:
              - 'apps/docs/**'
              - '**/*.md'

  lint:
    needs: changes
    if: needs.changes.outputs.code == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2  # Required for turbo affected detection

      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Lint
        run: pnpm turbo run lint

      - name: Type check
        run: pnpm turbo run type-check

  test:
    needs: changes
    if: needs.changes.outputs.code == 'true'
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:17-alpine
        env:
          POSTGRES_DB: ecosire_test
          POSTGRES_USER: ecosire
          POSTGRES_PASSWORD: password
        ports:
          - 5433:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Run unit tests
        run: pnpm turbo run test
        env:
          DATABASE_URL: postgresql://ecosire:password@localhost:5433/ecosire_test
          REDIS_URL: redis://localhost:6379

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./apps/api/coverage/lcov.info

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build all apps
        run: pnpm turbo run build
        env:
          NEXT_PUBLIC_API_URL: ${{ vars.STAGING_API_URL }}

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: |
            apps/web/.next/
            apps/api/dist/
          retention-days: 1  # Keep briefly for deploy job

ڈرامہ نگار کے ساتھ E2E ٹیسٹ جاب

  e2e:
    needs: build
    runs-on: ubuntu-latest
    strategy:
      matrix:
        project: [chromium, firefox, mobile-chrome]
      fail-fast: false  # Don't cancel other browsers if one fails

    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Install Playwright browsers
        run: cd apps/web && npx playwright install ${{ matrix.project }} --with-deps

      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-${{ github.sha }}

      - name: Start test servers
        run: |
          cd apps/api && node dist/main.js &
          cd apps/web && npx next start &
          npx wait-on http://localhost:3000 http://localhost:3001/health
        env:
          DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}

      - name: Run E2E tests
        run: cd apps/web && npx playwright test --project=${{ matrix.project }}
        env:
          BASE_URL: http://localhost:3000

      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report-${{ matrix.project }}
          path: apps/web/playwright-report/
          retention-days: 7

سیکیورٹی اسکیننگ ورک فلو

# .github/workflows/security.yml
name: Security Scan

on:
  schedule:
    - cron: '0 2 * * 1'  # Weekly Monday 2am UTC
  push:
    paths:
      - '**/package.json'
      - 'pnpm-lock.yaml'

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: '10.28.0'

      - name: Run pnpm audit
        run: pnpm audit --audit-level moderate
        continue-on-error: true  # Don't fail CI, just report

      - name: Run Snyk security scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          command: test
          args: --severity-threshold=high

  secret-scanning:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history for secret scanning

      - name: Run Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

تعیناتی ورک فلو

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deploy environment'
        required: true
        default: 'production'
        type: choice
        options:
          - production
          - staging

# Only one production deploy at a time
concurrency:
  group: deploy-${{ inputs.environment }}
  cancel-in-progress: false  # Don't cancel in-flight deploys

jobs:
  quality-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: '10.28.0'

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'pnpm'

      - run: pnpm install --frozen-lockfile

      - name: Run quality checks
        run: |
          pnpm turbo run lint type-check test
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}

  deploy:
    needs: quality-gate
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}  # Requires approval for production
    steps:
      - uses: actions/checkout@v4

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            set -e

            cd /opt/ecosire/app

            echo "Pulling latest changes..."
            git pull origin main

            echo "Installing dependencies..."
            pnpm install --frozen-lockfile

            echo "Building..."
            TURBO_TOKEN=${{ secrets.TURBO_TOKEN }} \
            TURBO_TEAM=${{ vars.TURBO_TEAM }} \
            npx turbo run build

            echo "Running migrations..."
            pnpm --filter @ecosire/db db:migrate

            echo "Restarting services..."
            pm2 restart ecosystem.config.cjs --update-env

            echo "Health check..."
            sleep 10
            curl -f https://ecosire.com/api/health || exit 1
            curl -f https://api.ecosire.com/api/health || exit 1

            echo "Deploy complete!"

      - name: Notify on success
        if: success()
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.repos.createCommitComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              commit_sha: context.sha,
              body: `Deployed to ${process.env.ENVIRONMENT} at ${new Date().toISOString()}`
            })
        env:
          ENVIRONMENT: ${{ inputs.environment }}

      - name: Notify on failure
        if: failure()
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: `Deploy failed: ${process.env.ENVIRONMENT}`,
              body: `Deploy to ${process.env.ENVIRONMENT} failed at ${new Date().toISOString()}\n\nRun: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
            })
        env:
          ENVIRONMENT: ${{ inputs.environment }}

کیشنگ کی حکمت عملی

مؤثر کیشنگ 2 منٹ اور 8 منٹ کی CI رنز کے درمیان فرق ہے:

# Reusable setup step (extract to composite action)
# .github/actions/setup-pnpm/action.yml
name: Setup pnpm
runs:
  using: composite
  steps:
    - uses: pnpm/action-setup@v4
      with:
        version: '10.28.0'

    - uses: actions/setup-node@v4
      with:
        node-version: '22'
        cache: 'pnpm'

    # Turbo cache (separate from pnpm cache)
    - uses: actions/cache@v4
      with:
        path: .turbo
        key: turbo-${{ runner.os }}-${{ github.sha }}
        restore-keys: |
          turbo-${{ runner.os }}-

    - name: Install dependencies
      run: pnpm install --frozen-lockfile
      shell: bash

سیکیورٹی کے بہترین طریقے

** راز کی بازگشت کبھی نہ کریں:**

# Wrong — secret appears in logs
- run: echo "API key is ${{ secrets.API_KEY }}"

# If you must generate and use runtime secrets
- run: |
    echo "::add-mask::$GENERATED_SECRET"
    GENERATED_SECRET=$(generate-secret)
    echo "GENERATED_SECRET=$GENERATED_SECRET" >> $GITHUB_ENV

** اجازتوں کو محدود کریں:**

# Minimum permissions at workflow level
permissions:
  contents: read
  actions: read
  checks: write  # Add only what you need

jobs:
  test:
    permissions:
      contents: read  # Override at job level if needed

پل کی درخواست کو انجیکشن سے روکیں:

# For PRs from forks — never expose secrets
on:
  pull_request:

jobs:
  test:
    # Secrets not available in fork PRs by default
    # Use pull_request_target only for trusted actions
    runs-on: ubuntu-latest

اکثر پوچھے گئے سوالات

میں صرف مخصوص پیکجز تبدیل ہونے پر ورک فلو کو کیسے متحرک کروں؟

dorny/paths-filter استعمال کریں یہ معلوم کرنے کے لیے کہ کون سے پیکجز تبدیل ہوئے ہیں، پھر فلٹر آؤٹ پٹ کو ملازمت کے حالات میں منتقل کریں۔ ٹربو کی بلٹ ان متاثرہ شناخت کے لیے، اپنے چیک آؤٹ مرحلے میں fetch-depth: 2 کو یقینی بنائیں تاکہ ٹربو کا موازنہ پچھلی کمٹ کے مقابلے کر سکے۔ pnpm turbo run test --filter="...[HEAD^1]" کا استعمال صرف تبدیل شدہ پیکجوں اور ان پر منحصر افراد کے لیے ٹیسٹ چلانے کے لیے کریں۔

میں CI میں ڈیٹا بیس کی منتقلی کو کیسے ہینڈل کروں؟

ہجرت کو ایک الگ کام میں چلائیں جو ٹیسٹوں کے بعد چلتا ہے لیکن حتمی تعمیراتی نمونہ بننے سے پہلے۔ CI میں ایک وقف شدہ ٹیسٹ ڈیٹا بیس URL کا استعمال کریں (ڈیو اور پروڈکشن سے الگ)۔ خود بخود CI سے پروڈکشن کے خلاف مائیگریشن کبھی نہ چلائیں — پروڈکشن ڈیٹا بیس کی تبدیلیوں کے لیے GitHub ماحولیاتی تحفظ کے قوانین کے ذریعے دستی منظوری کے ساتھ workflow_dispatch استعمال کریں۔

متعدد ماحول میں راز کو منظم کرنے کا بہترین طریقہ کیا ہے؟

تحفظ کے قواعد کے ساتھ GitHub ماحولیات (سیٹنگز> ماحولیات) استعمال کریں۔ staging اور production کے لئے الگ ماحول بنائیں۔ ہر ماحول کے تحت ماحول کے مخصوص راز کو ذخیرہ کریں، ذخیرہ کی سطح پر نہیں۔ مشترکہ رازوں کے لیے (جیسے ٹربو ریموٹ کیش ٹوکن)، ذخیرہ سطح کے راز استعمال کریں۔ جاب کنفیگریشن میں environment: کلید ماحول سے متعلق راز اور تحفظ کے اصولوں کو قابل بناتی ہے۔

میں CI میں pnpm انسٹال کو کیسے تیز کروں؟

cache: 'pnpm' کے ساتھ actions/setup-node@v4 pnpm اسٹور کو کیش کرتا ہے۔ بڑے monorepos کے لیے، کیشے کو بحال کرنے کے بعد pnpm install --prefer-offline بھی استعمال کریں۔ pnpm اسٹور کیشنگ اور ٹربو ریموٹ کیشنگ کا امتزاج عام طور پر کیش ہٹ پر انسٹال + بلڈ ٹائم کو 60-80% تک کم کرتا ہے۔

میں PRs بمقابلہ مین برانچ پشز کے لیے مختلف ورک فلو کیسے چلا سکتا ہوں؟

on ٹرگر شرائط استعمال کریں: on.push.branches مین کے لئے اور on.pull_request.branches PRs کے لئے۔ آپ PRs (لنٹ، یونٹ ٹیسٹ) اور مین پر مکمل سوٹ کے لیے ٹیسٹوں کا تیز تر سب سیٹ چلا سکتے ہیں۔ github.event_name == 'push' شرائط کا استعمال کریں ڈیپلائی جابز کو صرف پش ٹو مین پر، PR ایونٹس پر نہیں۔


اگلے اقدامات

ایک اچھی طرح سے ڈیزائن کی گئی CI/CD پائپ لائن ایک ہائی-ویلوسٹی انجینئرنگ ٹیم کی بنیاد ہے — یہ آپ کو بار بار جہاز بھیجنے اور خود بخود ریگریشن پکڑنے کا اعتماد دیتی ہے۔ ECOSIRE GitHub Actions CI کو lint، ٹائپ-چیک، 1,301 یونٹ ٹیسٹ، پلے رائٹ E2E، اور صفر-ڈاؤن ٹائم SSH تعیناتیوں کے ساتھ چلاتا ہے۔

چاہے آپ کو CI/CD فن تعمیر سے متعلق مشاورت، Turborepo monorepo سیٹ اپ، یا مکمل DevOps سپورٹ کی ضرورت ہو، ہماری انجینئرنگ سروسز کو دریافت کریں۔

E

تحریر

ECOSIRE Research and Development Team

ECOSIRE میں انٹرپرائز گریڈ ڈیجیٹل مصنوعات بنانا۔ Odoo انٹیگریشنز، ای کامرس آٹومیشن، اور AI سے چلنے والے کاروباری حل پر بصیرت شیئر کرنا۔

Chat on WhatsApp